SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
6.1 KB · 121 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { notFound } from "next/navigation";4import { ArrowRight, Swords } from "lucide-react";5import { Container, Eyebrow } from "@/components/marketing/section";6import { Button } from "@/components/ui/button";7import { ProviderIcon } from "@/components/brand/provider-icon";8import { CompareTable } from "@/components/models/compare-table";9import { PROVIDERS } from "@/lib/client/providers";10import { getPublicModels, flagshipModels } from "@/lib/models/public-data";11import { resolveCompareSlug, compareSlug, parseCompareSlug } from "@/lib/models/slug";12import { formatContext, formatPrice } from "@/lib/models/format";1314export const dynamic = "force-dynamic";1516type Params = Promise<{ slug: string }>;1718async function resolve(slug: string) {19  if (!parseCompareSlug(slug)) return null;20  const { models, ctx } = await getPublicModels();21  const chosen = resolveCompareSlug(models, slug);22  return chosen ? { chosen, models, ctx } : null;23}2425function names(models: { displayName: string }[]): string {26  return models.map((m) => m.displayName).join(" vs ");27}2829export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {30  const { slug } = await params;31  const r = await resolve(slug);32  if (!r) return { title: "Comparison not found", robots: { index: false } };33  const title = `${names(r.chosen)} — pricing, context & capabilities`;34  const description = r.chosen.map((m) => `${m.displayName} (${PROVIDERS[m.provider].shortName}): ${formatPrice(m.pricing?.inputPerMillion)} in / ${formatPrice(m.pricing?.outputPerMillion)} out per 1M tokens, ${formatContext(m.limits?.contextTokens)} context`).join(" · ") + ". Compared side by side on PolyLLM.";35  const canonical = `/compare/${compareSlug(r.chosen)}`;36  return { title, description, alternates: { canonical }, openGraph: { title: `${names(r.chosen)} — PolyLLM`, description, url: canonical } };37}3839export default async function ComparePage({ params }: { params: Params }) {40  const { slug } = await params;41  const r = await resolve(slug);42  if (!r) notFound();43  const { chosen, models, ctx } = r;44  const arenaHref = `/app/arena?models=${encodeURIComponent(chosen.map((m) => m.key).join(","))}`;45  const chosenKeys = new Set(chosen.map((m) => m.key));46  const related = flagshipModels(models, 6)47    .filter((f) => !chosenKeys.has(f.key))48    .slice(0, 4)49    .map((f) => ({ f, slug: compareSlug([chosen[0], f]) }));5051  return (52    <>53      <section className="border-b border-border bg-bg-subtle/60 py-12 sm:py-16">54        <Container>55          <Eyebrow>Model comparison</Eyebrow>56          <h1 className="mt-3 max-w-3xl text-balance text-3xl font-semibold leading-[1.08] tracking-tight sm:text-5xl">57            {chosen.map((m, i) => (58              <span key={m.key}>59                {i > 0 ? <span className="text-fg-subtle"> vs </span> : null}60                {m.displayName}61              </span>62            ))}63          </h1>64          <p className="mt-4 max-w-2xl text-pretty text-[15px] leading-7 text-fg-muted sm:text-base">65            {chosen.map((m) => `${PROVIDERS[m.provider].name}’s ${m.displayName}`).join(" and ")} compared on price per million tokens, context window, output limits, reasoning controls and every capability PolyLLM tracks — from the providers’ own listings, nothing guessed.66          </p>67          <div className="mt-6 flex flex-col gap-2 sm:flex-row">68            <Button asChild size="lg" className="w-full sm:w-auto">69              <Link href={arenaHref}>70                <Swords /> Try {chosen.length === 2 ? "both" : "all"} in Arena71              </Link>72            </Button>73            <Button asChild size="lg" variant="ghost" className="w-full sm:w-auto">74              <Link href="/models">Browse the full catalog</Link>75            </Button>76          </div>77        </Container>78      </section>7980      <Container className="py-10 sm:py-14">81        <CompareTable models={chosen} ctx={ctx} />8283        {related.length ? (84          <section className="mt-16">85            <h2 className="text-lg font-semibold tracking-tight">More comparisons with {chosen[0].displayName}</h2>86            <ul className="mt-4 grid gap-2 sm:grid-cols-2">87              {related.map(({ f, slug: s }) => (88                <li key={s}>89                  <Link href={`/compare/${s}`} className="panel flex items-center gap-3 p-3 transition-colors hover:bg-bg-muted">90                    <ProviderIcon provider={f.provider} size={16} />91                    <span className="min-w-0 flex-1 truncate text-[13.5px] font-medium">92                      {chosen[0].displayName} <span className="text-fg-subtle">vs</span> {f.displayName}93                    </span>94                    <ArrowRight className="size-4 shrink-0 text-fg-subtle" />95                  </Link>96                </li>97              ))}98            </ul>99          </section>100        ) : null}101102        <section className="mt-16 rounded-2xl border border-border bg-bg-subtle/60 p-6 text-center sm:p-10">103          <h2 className="text-balance text-2xl font-semibold tracking-tight sm:text-3xl">Run the same prompt on {chosen.length === 2 ? "both" : "all of them"}.</h2>104          <p className="mx-auto mt-3 max-w-md text-[15px] leading-7 text-fg-muted">PolyLLM Arena streams the answers side by side with latency, tokens and cost — with your own API keys, encrypted.</p>105          <div className="mt-6 flex flex-col justify-center gap-2 sm:flex-row">106            <Button asChild size="lg" className="w-full sm:w-auto">107              <Link href="/signup">108                Create a free account <ArrowRight />109              </Link>110            </Button>111            <Button asChild size="lg" variant="ghost" className="w-full sm:w-auto">112              <Link href={arenaHref}>Open Arena</Link>113            </Button>114          </div>115          <p className="mt-6 text-[11px] leading-5 text-fg-subtle">Prices are the providers’ published list prices in USD per million tokens at the last catalog audit. Release dates and knowledge cutoffs appear only when the provider publishes them.</p>116        </section>117      </Container>118    </>119  );120}121